package com.yuan.common.redis.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

@SuppressWarnings("ConstantConditions")
@Slf4j
@Component
public class RedisRepository {
    /**
     * 默认编码
     */
    private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

    /**
     * key 序列化
     */
    private static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
    /**
     * value 序列化
     */
    private static final JdkSerializationRedisSerializer JDK_SERIALIZER = new JdkSerializationRedisSerializer();

    private final RedisTemplate<String, String> redisTemplate;

    public RedisRepository(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.redisTemplate.setKeySerializer(STRING_SERIALIZER);
        this.redisTemplate.setHashKeySerializer(JDK_SERIALIZER);
    }

    /**
     * 获取 RedisTemplate对象
     */
    public RedisTemplate<String, String> getRedisTemplate() {
        return redisTemplate;
    }
    /**
     * 获取 RedisSerializer
     *
     * @return the redis serializer
     */
    protected RedisSerializer<String> getRedisSerializer() {
        return redisTemplate.getStringSerializer();
    }

    public void set(final String key, final String value){
        redisTemplate.opsForValue().set(key, value);
    }
    /**
     * 一次性添加数组到   过期时间的  缓存，不用多次连接，节省开销
     *
     * @param keys   the keys
     * @param values the values
     */
    public void set(final String[] keys, final String[] values) {
        if (keys.length != values.length) {
            throw new IllegalArgumentException("键数组和值数组的长度必须相同");
        }
        Map<String, String> keyValueMap = new HashMap<>();
        for (int i = 0; i < keys.length; i++) {
            keyValueMap.put(keys[i], values[i]);
            log.debug("[redisTemplate redis]准备放入 缓存  url:{}", keys[i]);
        }
        // 使用 multiSet 方法批量设置键值对
        redisTemplate.opsForValue().multiSet(keyValueMap);
    }
    /**
     * 添加到带有 过期时间的  缓存
     *
     * @param key   redis主键
     * @param value 值
     * @param time  过期时间
     */
    public void setExpire(final String key, final String value, final long time) {
        redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
        log.info("[redisTemplate redis]放入 缓存  url:{} ========缓存时间为{}秒".formatted(key, time));
    }
    /**
     * 获取key过期剩余时间
     *
     * @param key
     * @param timeUnit
     * @return
     */
    public Long getExpire(final String key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }
    /**
     * 对某个主键对应的值加一,value值必须是全数字的字符串
     *
     * @param key the key
     * @return the long
     */
    public long incr(final String key) {
       Long inc = redisTemplate.opsForValue().increment(key);
        return Objects.requireNonNullElse(inc, null);
    }

    public long decr(final String key) {
        Long decr = redisTemplate.opsForValue().decrement(key);
        return Objects.requireNonNullElse(decr, null);
    }
    /**
     * 查询在以keyPatten的所有  key
     *
     * @param keyPatten the key patten
     * @return the set
     */
    public Set<String> keys(final String keyPatten) {
        return redisTemplate.execute((RedisCallback<Set<String>>) connection -> redisTemplate.keys(keyPatten + "*"));
    }
    /**
     * 根据key获取对象
     *
     * @param keys the key
     * @return the byte [ ]
     */
    public List<String> get(final List<String> keys) {
        // 使用 multiGet 方法批量查询多个 key 的值
        List<String> values = redisTemplate.opsForValue().multiGet(keys);
        for (String key : keys) {
            log.debug("[redisTemplate redis]取出 缓存  url:{} ", key);
        }
        return values;
    }
    /**
     * 根据key获取对象
     *
     * @param key the key
     * @return the string
     */
    public String get(final String key) {
        return redisTemplate.opsForValue().get(key);
    }
    /**
     * 根据key获取对象
     *
     * @param keyPatten the key patten
     * @return the keys values
     */
    public Map<String, String> getKeysValues(final String keyPatten) {
        log.debug("[redisTemplate redis]  getValues()  patten={} ", keyPatten);
        RedisSerializer<String> serializer = getRedisSerializer();
        Map<String, String> maps = new HashMap<>();
        ScanOptions scanOptions = ScanOptions.scanOptions().match(keyPatten + "*").build();
        try (Cursor<String> cursor = redisTemplate.scan(scanOptions)) {
            List<String> keys = new ArrayList<>();
            while (cursor.hasNext()) {
                String key = cursor.next();
                keys.add(key);
            }
            if (!keys.isEmpty()) {
                List<String> values = redisTemplate.opsForValue().multiGet(keys);
                for (int i = 0; i < keys.size(); i++) {
                    if (values != null) {
                        maps.put(keys.get(i), values.get(i));
                    }
                }
            }
        }
        return maps;
    }
    /**
     * Ops for hash hash operations.
     *
     * @return the hash operations
     */
    public HashOperations<String, String, String> opsForHash() {
        return redisTemplate.opsForHash();
    }
    /**
     * 对HashMap操作
     *
     * @param key       the key
     * @param hashKey   the hash key
     * @param hashValue the hash value
     */
    public void putHashValue(String key, String hashKey, String hashValue) {
        log.debug("[redisTemplate redis]  putHashValue()  key={},hashKey={},hashValue={} ", key, hashKey, hashValue);
        opsForHash().put(key, hashKey, hashValue);
    }
    /**
     * 获取单个field对应的值
     *
     * @param key     the key
     * @param hashKey the hash key
     * @return the hash values
     */
    public Object getHashValues(String key, String hashKey) {
        log.debug("[redisTemplate redis]  getHashValues()  key={},hashKey={}", key, hashKey);
        return opsForHash().get(key, hashKey);
    }
    /**
     * 根据key值删除
     *
     * @param key      the key
     * @param hashKeys the hash keys
     */
    public void delHashValues(String key, Object... hashKeys) {
        log.debug("[redisTemplate redis]  delHashValues()  key={}", key);
        opsForHash().delete(key, hashKeys);
    }
    /**
     * key只匹配map
     *
     * @param key the key
     * @return the hash value
     */
    public Map<String, String> getHashValue(String key) {
        log.debug("[redisTemplate redis]  getHashValue()  key={}", key);
        return opsForHash().entries(key);
    }
    /**
     * 批量添加
     *
     * @param key the key
     * @param map the map
     */
    public void putHashValues(String key, Map<String, String> map) {
        opsForHash().putAll(key, map);
    }

    /**
     * 集合数量
     *
     * @return the long
     */
    public long dbSize() {
        return redisTemplate.execute(RedisServerCommands::dbSize);
    }
    /**
     * 清空redis存储的数据
     *
     * @return the string
     */
    public String flushDB() {
        try {
            redisTemplate.execute((RedisCallback<Void>) connection -> {
                RedisServerCommands serverCommands = connection.serverCommands();
                serverCommands.flushDb();
                return null;
            });
            return "ok";
        } catch (Exception e) {
            log.debug( "清空 Redis 数据库时出现异常", e);
            return "error";
        }
    }
    /**
     * 判断某个主键是否存在
     *
     * @param key the key
     * @return the boolean
     */
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 删除key
     *
     * @param keys the keys
     * @return the long
     */
    public long del(final String... keys) {
        return redisTemplate.delete(Arrays.asList(keys));
    }
    /**
     * redis List 引擎 链表
     *
     * @return the list operations
     */
    public ListOperations<String, String> opsForList() {
        return redisTemplate.opsForList();
    }
    /**
     * redis List数据结构 : 将一个或多个值 value 插入到列表 key 的表头
     *
     * @param key   the key
     * @param value the value
     * @return the long
     */
    public Long leftPush(String key, String value) {
        return opsForList().leftPush(key, value);
    }
    /**
     * redis List数据结构 : 移除并返回列表 key 的头元素
     *
     * @param key the key
     * @return the string
     */
    public String leftPop(String key) {
        return opsForList().leftPop(key);
    }
    /**
     * redis List数据结构 :将一个或多个值 value 插入到列表 key 的表尾(最右边)。
     *
     * @param key   the key
     * @param value the value
     * @return the long
     */
    public Long in(String key, String value) {
        return opsForList().rightPush(key, value);
    }
    /**
     * redis List数据结构 : 移除并返回列表 key 的末尾元素
     *
     * @param key the key
     * @return the string
     */
    public String rightPop(String key) {
        return opsForList().rightPop(key);
    }
    /**
     * redis List数据结构 : 返回列表 key 的长度 ; 如果 key 不存在，则 key 被解释为一个空列表，返回 0 ; 如果 key 不是列表类型，返回一个错误。
     *
     * @param key the key
     * @return the long
     */
    public Long length(String key) {
        return opsForList().size(key);
    }
    /**
     * redis List数据结构 : 根据参数 i 的值，移除列表中与参数 value 相等的元素
     *
     * @param key   the key
     * @param i     the
     * @param value the value
     */
    public void remove(String key, long i, String value) {
        opsForList().remove(key, i, value);
    }
    /**
     * redis List数据结构 : 将列表 key 下标为 index 的元素的值设置为 value
     *
     * @param key   the key
     * @param index the index
     * @param value the value
     */
    public void set(String key, long index, String value) {
        opsForList().set(key, index, value);
    }
    /**
     * redis List数据结构 : 返回列表 key 中指定区间内的元素，区间以偏移量 start 和 end 指定。
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     * @return the list
     */
    public List<String> getList(String key, int start, int end) {
        return opsForList().range(key, start, end);
    }
    /**
     * redis List数据结构 : 批量存储
     *
     * @param key  the key
     * @param list the list
     * @return the long
     */
    public Long leftPushAll(String key, List<String> list) {
        return opsForList().leftPushAll(key, list);
    }
    /**
     * redis List数据结构 : 批量存储
     *
     * @param key  the key
     * @param list the list
     * @return the long
     */
    public Long pushAll(String key, List<String> list) {
        return opsForList().rightPushAll(key, list);
    }
    /**
     * redis List数据结构 : 将值 value 插入到列表 key 当中，位于值 index 之前或之后,默认之后。
     *
     * @param key   the key
     * @param index the index
     * @param value the value
     */
    public void insert(String key, long index, String value) {
        opsForList().set(key, index, value);
    }
    /**
     * redis zset 引擎 集合
     *
     * @return the list operations
     */
    public ZSetOperations<String, String> opsForzSet() {
        return redisTemplate.opsForZSet();
    }
    /**
     * 有序集合
     * redis zset数据结构 : 将值 value 插入到列表 key 当中，分数为score。
     *
     * @param key   the key
     * @param score the score
     * @param value the value
     */
    public Boolean zsetAdd(String key, String value, double score) {
        return opsForzSet().add(key, value, score);
    }
    /**
     * redis zset数据结构 : 从小到大
     *
     * @param key   the key
     * @param min the score
     * @param max the value
     */
    public Set<String> zsetRangByScore(String key, double min, double max) {
        return opsForzSet().rangeByScore(key, min, max);
    }
    /**
     * redis zset数据结构 : start,end,从小到大
     *
     * @param key   the key
     * @param start the start
     * @param end   the end
     */
    public Set<String> zsetRang(String key, long start, long end) {
        return opsForzSet().range(key, start, end);
    }
    public Double zsetIncrScore(String key, String value, double score) {
        return opsForzSet().incrementScore(key, value, score);
    }
    /**
     * redis zset数据结构 : 从大到小
     *
     * @param key   the key
     * @param min the score
     * @param max the value
     */
    public Set<String> zsetRevRangByScore(String key, double min, double max) {
        return opsForzSet().reverseRangeByScore(key, min, max);
    }
    /**
     * 有序集合从大到小
     * end 为-1 获取所有
     * redis zset数据结构 : start,end
     * @param key   the key
     * @param start the score
     * @param end the value
     */
    public Set<String> zsetRevRang(String key, long start, long end) {
        return opsForzSet().reverseRange(key, start, end);
    }
    /**
     * redis zset数据结构 : start,end,从大到小
     *
     * @param key   the key
     */
    public Long zsetCount(String key) {
        return opsForzSet().size(key);
    }
    /**
     * redis zset 根据score删除数据 :
     *
     * @param key   the key
     * @param score the score
     */
    public void zsetRmByScore(String key, long score) {
        opsForzSet().removeRangeByScore(key, score, score);
    }

    public void zsetRemove(String key, String... obj) {
        opsForzSet().remove(key, (Object[]) obj);
    }

    public void zsetRemoveRange(String key, long start, long end) {
        opsForzSet().removeRange(key, start, end);
    }
    /**
     * redis zset 模糊查询 :
     *
     * @param key   the key
     * @param param the param
     */
    public List<String> keyLikeValue(String key, String param) {
        ZSetOperations<String, String> zSetOps = redisTemplate.opsForZSet();
        ScanOptions scanOptions = ScanOptions.scanOptions().match("*" + param + "*").build();
        List<String> list = new ArrayList<>();
        try (Cursor<ZSetOperations.TypedTuple<String>> cursor = zSetOps.scan(key, scanOptions)) {
            while (cursor.hasNext()) {
                ZSetOperations.TypedTuple<String> tuple = cursor.next();
                if (tuple != null) {
                    list.add(tuple.getValue());
                }
            }
        }
        return list;
    }
}
